#include <stdlib.h>
#include <stdarg.h>
#include <string.h>

#include <common/arity.h>
#include <common/macro/build.h>
#include <common/util.h>
#include <query/condition.h>
#include <query/result.h>
#include <query/query.h>

/* Atypically, this won't be public. */
API_FILELOCAL condition_t *condition_new() {
	condition_t *condition;

	condition = malloc(sizeof(*condition));
	condition->transaction = NULL;

	return condition;
}

API_PUBLIC condition_t *condition_predicate_new_list(int column_id, char *name, int argc, size_t *argsizes, char **arguments) {
	int i;
	condition_t *condition;

	condition = condition_new();

	condition->type = CONDITION_PREDICATE;
	condition->positive = 1;
	condition->predicate.name = name;
	condition->predicate.column_id = column_id;
	condition->predicate.argc = argc;

	/* Make a copy. */
	condition->predicate.argsizes = malloc(sizeof(*condition->predicate.argsizes) * argc);
	memcpy(condition->predicate.argsizes, argsizes, sizeof(*condition->predicate.argsizes) * argc);
	condition->predicate.arguments = malloc(sizeof(*condition->predicate.arguments) * argc);
	for (i = 0; i < argc; i++) {
		condition->predicate.arguments[i] = util_memdup(arguments[i], argsizes[i]);
	}

	return condition;
}

API_PUBLIC condition_t *condition_predicate_new(int column_id, char *name, int argc, ...) {
	char *arg;
	va_list ap;
	condition_t *condition;
	int i;

	condition = condition_new();

	condition->type = CONDITION_PREDICATE;
	condition->positive = 1;
	condition->predicate.name = name;
	condition->predicate.column_id = column_id;
	condition->predicate.argc = argc;

	condition->predicate.argsizes = malloc(sizeof(*condition->predicate.argsizes) * argc);
	condition->predicate.arguments = malloc(sizeof(*condition->predicate.arguments) * argc);

	va_start(ap, argc);
	for (i = 0; i < argc; i++) {
		arg = va_arg(ap, char *);
		condition->predicate.argsizes[i] = va_arg(ap, size_t);
		condition->predicate.arguments[i] = util_memdup(arg, condition->predicate.argsizes[i]);
	}
	va_end(ap);

	return condition;
}

API_PUBLIC condition_t *condition_callback_new(query_callback_f funcptr, void *ctx) {
	condition_t *condition;

	condition = condition_new();

	condition->type = CONDITION_CALLBACK;
	condition->positive = 1;
	condition->callback.funcptr = funcptr;
	condition->callback.ctx = ctx;

	return condition;
}

API_PUBLIC condition_t *condition_negate(condition_t *condition) {
	if (condition == NULL) return NULL;
	condition->positive = !condition->positive;
	return condition;
}

API_LIBLOCAL int condition_bind(condition_t *condition, transaction_t *transaction, table_t *table) {
	int i;
	int column_id;
	void *arguments;
	predicate_t *predicate;

	/* Nothing needs to be done, condition is already bound. */
	if (condition->transaction != NULL)
		return 0;

	/* Predicates need compilation, callbacks don't. */	
	if (condition->type == CONDITION_PREDICATE) {
		column_id = condition->predicate.column_id;

		/* Step 1: Look up predicate. */
		predicate = datatype_predicate_lookup(table->columns[column_id]->datatype, condition->predicate.name);
		if (predicate == NULL)
			return -1;

		/* Step 2: Use predicate-local functions to compile arguments. */
		if (!arity_check(predicate->minargs, predicate->maxargs, condition->predicate.argc))
			return -1;

		if (predicate->compile != NULL) {
			arguments = predicate->compile(table->columns[column_id]->dt_args, condition->predicate.argsizes, condition->predicate.arguments, condition->predicate.argc);
			if (arguments == NULL)
				return -1;
		} else {
			arguments = NULL;
		}

		/* Step 3: Release old data. */
		for (i = 0; i < condition->predicate.argc; i++) {
			free(condition->predicate.arguments[i]);
		}
		free(condition->predicate.argsizes);
		free(condition->predicate.arguments);

		/* Step 4: Set the compiled data. */
		condition->compiled.column_id = column_id;
		condition->compiled.predicate = predicate;
		condition->compiled.arguments = arguments;
	}

	/* Mark the condition as bound to the given transaction. */
	condition->transaction = transaction;

	return 0;
}

API_LIBLOCAL int condition_check(condition_t *condition, query_t *query, void **values) {
	char *data;
	size_t size;
	int rv;

	switch (condition->type) {
		case CONDITION_PREDICATE:
			query_data_get(query, condition->compiled.column_id, &data, &size);

			rv = condition->compiled.predicate->execute(data, size,
					condition->compiled.arguments,
					&values[condition->compiled.column_id]
			);

			if (condition->positive)
				return rv;
			else
				return !rv;

		case CONDITION_CALLBACK:
			return condition->callback.funcptr(query, condition->callback.ctx);
	}

	return 0;
}

API_PUBLIC void condition_destroy(condition_t *condition) {
	int i;

	if (condition == NULL) return;

	switch (condition->type) {
		case CONDITION_PREDICATE:
			if (condition->transaction != NULL) {
				condition->compiled.predicate->destroy(condition->compiled.arguments);
			} else {
				for (i = 0; i < condition->predicate.argc; i++) {
					free(condition->predicate.arguments[i]);
				}
				free(condition->predicate.argsizes);
				free(condition->predicate.arguments);
			}
			break;

		case CONDITION_CALLBACK:
			break;
	}

	free(condition);
}
